﻿using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Data;
using System.IO;
using System.Configuration;
using System.Web.Configuration;
using System.Security.Cryptography;

namespace WcfService
{
    // NOTE: If you change the interface name "IMTom" here, you must also update the reference to "IMTom" in App.config.
    [ServiceContract(Namespace = "http://WcfService")]
    public interface IMTom
    {
        [OperationContract]
        DataSet Query(string query);

        [OperationContract]
        long GetMaxRequestLength();

        [OperationContract]
        byte[] DownloadChunk(string FileName, long Offset, int BufferSize);

        [OperationContract]
        long GetFileSize(string FileName);

        [OperationContract]
        List<string> GetFilesList();

        [OperationContract]
        string CheckFileHash(string FileName);

        [OperationContract]
        void AppendChunk(string FileName, byte[] buffer, long Offset);

    }

    // NOTE: If you change the class name "MTom" here, you must also update the reference to "MTom" in App.config.
    public partial class Service : IMTom
    {
        /// <summary>
        /// Allow Query
        /// </summary>
        /// <param name="sqlQuery">Select ALL</param>
        /// <returns></returns>
        public DataSet Query(string sqlQuery)
        {
            return  dsQuery(sqlQuery);
        }

        private string UploadPath = "";

        public void GetFilePath()
        {
            UploadPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + ConfigurationManager.AppSettings["UploadPath"].ToString();
            if (!Directory.Exists(UploadPath))
                throw new Exception("Upload Folder not found. \nThe folder " + UploadPath + " does not exist");
        }

        /// <summary>
        /// The winforms client needs to know what is the max size of chunk that the server 
        /// will accept.  this is defined by MaxRequestLength, which can be overridden in
        /// web.config.
        /// </summary>
        public long GetMaxRequestLength()
        {
            try
            {
                return (ConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection).MaxRequestLength;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.GetType().Name + " \n" + ex.Message);
            }
        }
        #region Upload
        /// <summary>
        /// Append a chunk of bytes to a file.
        /// The client should ensure that all messages are sent in sequence. 
        /// This method always overwrites any existing file with the same name
        /// </summary>
        /// <param name="FileName">The name of the file that this chunk belongs to, e.g. Vista.ISO</param>
        /// <param name="buffer">The byte array, i.e. the chunk being transferred</param>
        /// <param name="Offset">The offset at which to write the buffer to</param>

        public void AppendChunk(string FileName, byte[] buffer, long Offset)
        {
            GetFilePath();
            string FilePath = Path.Combine(UploadPath, FileName);

            if (Offset == 0)	// new file, create an empty file
                File.Create(FilePath).Close();

            // open a file stream and write the buffer.  Don't open with FileMode.Append because the transfer may wish to start a different point
            using (FileStream fs = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
            {
                fs.Seek(Offset, SeekOrigin.Begin);
                fs.Write(buffer, 0, buffer.Length);
            }
        }
        #endregion

        #region download

        /// <summary>
        /// Download a chunk of a file from the Upload folder on the server. 
        /// </summary>
        /// <param name="FileName">The FileName to download</param>
        /// <param name="Offset">The offset at which to fetch the next chunk</param>
        /// <param name="BufferSize">The size of the chunk</param>
        /// <returns>The chunk as a byte[]</returns>

        public byte[] DownloadChunk(string FileName, long Offset, int BufferSize)
        {
            GetFilePath();
            string FilePath = Path.Combine(UploadPath, FileName);

            // check that requested file exists
            if (!File.Exists(FilePath))
                throw new Exception("File not found \n" + String.Format("The file {0} does not exist", FilePath));

            long FileSize = new FileInfo(FilePath).Length;

            // if the requested Offset is larger than the file, quit.
            if (Offset > FileSize)
                throw new Exception("Invalid Download Offset \n" + String.Format("The file size is {0}, received request for offset {1}", FileSize, Offset));

            // open the file to return the requested chunk as a byte[]
            byte[] TmpBuffer;
            int BytesRead;

            try
            {
                using (FileStream fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    fs.Seek(Offset, SeekOrigin.Begin);	// this is relevent during a retry. otherwise, it just seeks to the start
                    TmpBuffer = new byte[BufferSize];
                    BytesRead = fs.Read(TmpBuffer, 0, BufferSize);	// read the first chunk in the buffer (which is re-used for every chunk)
                }
                if (BytesRead != BufferSize)
                {
                    // the last chunk will almost certainly not fill the buffer, so it must be trimmed before returning
                    byte[] TrimmedBuffer = new byte[BytesRead];
                    Array.Copy(TmpBuffer, TrimmedBuffer, BytesRead);
                    return TrimmedBuffer;
                }
                else
                    return TmpBuffer;
            }
            catch (Exception ex)
            {
                throw new Exception("Error reading file \n" + ex.Message);
                return null;
            }
        }

        /// <summary>
        /// Get the number of bytes in a file in the Upload folder on the server.
        /// The client needs to know this to know when to stop downloading
        /// </summary>

        public long GetFileSize(string FileName)
        {
            GetFilePath();
            string FilePath = UploadPath + "\\" + FileName;

            // check that requested file exists
            if (!File.Exists(FilePath))
                throw new Exception("File not found \nThe file " + FilePath + " does not exist");

            return new FileInfo(FilePath).Length;
        }

        /// <summary>
        /// Return a list of filenames from the Upload folder on the server
        /// </summary>

        public List<string> GetFilesList()
        {
            GetFilePath();

            List<string> files = new List<string>();
            foreach (string s in Directory.GetFiles(UploadPath))
                files.Add(Path.GetFileName(s));
            return files;
        }
        #endregion

        #region file hashing
        public string CheckFileHash(string FileName)
        {
            GetFilePath();
            string FilePath = UploadPath + "\\" + FileName;
            MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
            byte[] hash;
            using (FileStream fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096))
                hash = md5.ComputeHash(fs);
            return BitConverter.ToString(hash);
        }
        #endregion

        #region Exception Handling
        /// <summary>
        /// Throws a soap exception.  It is formatted in a way that is more readable to the client, after being put through the xml serialisation process
        /// Typed exceptions don't work well across web services, so these exceptions are sent in such a way that the client
        /// can determine the 'name' or type of the exception thrown, and any message that went with it, appended after a : character.
        /// </summary>
        /// <param name="exceptionName"></param>
        /// <param name="message"></param>
        //public static void CustomSoapException(string exceptionName, string message)
        //{
        //    throw new System.Web.Services.Protocols.SoapException(exceptionName + ": " + message, new System.Xml.XmlQualifiedName("BufferedUpload"));
        //}

        #endregion
    }
}
